package com.edu.networkexperimentation.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.edu.networkexperimentation.common.DataUtil;
import com.edu.networkexperimentation.common.ErrorCode;
import com.edu.networkexperimentation.exception.BusinessException;
import com.edu.networkexperimentation.mapper.QuestionMapper;
import com.edu.networkexperimentation.mapper.UserMapper;
import com.edu.networkexperimentation.model.algorithm.FitnessUtils;
import com.edu.networkexperimentation.model.algorithm.Individual;
import com.edu.networkexperimentation.model.algorithm.PaperResult;
import com.edu.networkexperimentation.model.domain.Question;
import com.edu.networkexperimentation.model.domain.Record;
import com.edu.networkexperimentation.service.GeneticService;
import com.edu.networkexperimentation.service.RecordService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;

import javax.annotation.Resource;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.util.*;

@Service
@Slf4j
public class GeneticImpl extends ServiceImpl<QuestionMapper, Question> implements GeneticService {
    private long stuid;
    private double T0;
    private double alpha = 0.98;
    private double diff;
    private double dist;
    private final int loops = 100;
    private final int chapters = 3;
    private final int questionTypes = 3;
    private final int questionsnums = 10;
    private double allFitness;
    private final double pre_fitness = 0.98;
    private final double change_rate = 0.02;
    private int[] nums = new int[questionTypes];
    private int iterationNum = 0;
    private double knowledgeCoverage = 0;
    private final List<Integer> rates = new ArrayList<>();

    private List<Individual> population;

    private final SecureRandom secureRandom = new SecureRandom();

    private final int population_size = 30;
    private final List<List<Question>> questionList = new ArrayList<>();
    //    private final List<Set<Integer>> population_questions = new ArrayList<>();
    private List<Double> fitnessList;
    private List<Double> averageFitnessList;
    private double current_fitness;
    private double[] interest;

    static private final String[] types = {"选择", "判断", "简答"};
    private final int[] type_nums = new int[3];
    private List<Individual> basePopulation;

    @Resource
    private QuestionMapper questionMapper;

    @Resource
    private UserMapper userMapper;

    @Resource
    private RecordService recordService;

    private void init_paper(int[] nums) {
        String seed = "genetic_paper" + System.currentTimeMillis();
        secureRandom.setSeed(seed.getBytes());
        population = new ArrayList<>();
        rates.clear();
        QueryWrapper<Record> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(Record::getUserid, stuid).orderByAsc(Record::getSectionid);
        List<Record> records = recordService.list(queryWrapper);
        records.forEach(record -> {
            rates.add(record.getNum());
        });

        // 获取所有对应题型过程所有的可能, 并初始化试卷
        for (int j = 0; j < questionTypes; j++) {
            List<Question> questionsList = questionMapper
                    .selectList(new QueryWrapper<Question>().eq("type", j));
            this.questionList.add(questionsList);
            type_nums[j] = questionsList.size();
            if (questionsList.size() < nums[j]) {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "类型为" + types[j] + "的题目不足");
            }
        }

        for (int i = 0; i < population_size; i++) {
            Set<Integer> ids = new HashSet<>();
            Individual individual = new Individual();
            individual.setId(i);
            for (int j = 0; j < questionTypes; j++) {
                for (int k = 0; k < nums[j]; k++) {
                    int index = secureRandom.nextInt(type_nums[j]);
                    while (ids.contains(index)) {
                        index = secureRandom.nextInt(type_nums[j]);
                    }
                    ids.add(index);
                    individual.getQuestions().add(questionList.get(j).get(index));
                }
            }
            population.add(individual);
        }
    }

    //计算试卷的适应度 diff和dist 分别为用户指定的难度和区分度
    private List<Double> get_Fitness() {
        List<Double> result = new ArrayList<>();
        allFitness = 0;
        for (Individual individual : this.population) {
            double fitness = FitnessUtils.getFitness(individual, this.diff, this.rates);
            individual.setFitness(fitness);
            result.add(DataUtil.des(fitness));
            allFitness += fitness;
        }
        return result;
    }

    // 选择算子
    private void select() {
        List<Individual> newPopulation = new ArrayList<>();
        double[] rates = new double[this.population_size];
        for (int i = 0; i < this.population_size; i++) {
            Individual individual = this.population.get(i);
            rates[i] = individual.getFitness() / allFitness;
            if (i > 0) {
                rates[i] += rates[i - 1];
            }
        }

        Set<Integer> ids = new HashSet<>();
        for (int i = 0; i < this.population_size; i++) {
            double flag = secureRandom.nextDouble();
            int j = 0;
            while (j < this.population_size && flag > rates[j]) {
                j++;
            }
            if (!ids.contains(j)) {
                ids.add(j);
                Individual individual = this.population.get(j);
                individual.setId(newPopulation.size() + 1);
                newPopulation.add(individual);
            }
        }
        this.population = newPopulation;
    }

    private void new_select() {
        List<Individual> newPopulation = new ArrayList<>();
        double[] rates = new double[this.population_size];
        for (int i = 0; i < this.population_size; i++) {
            Individual individual = this.population.get(i);
            rates[i] = individual.getFitness() / allFitness;
            if (i > 0) {
                rates[i] += rates[i - 1];
            }
        }
        this.population.sort(Comparator.comparing(Individual::getFitness));
        this.basePopulation = this.population.subList(this.population_size / 10 * 9, this.population_size);
        Set<Integer> ids = new HashSet<>();
        for (int i = 0; i < this.population_size; i++) {
            double flag = secureRandom.nextDouble();
            int j = 0;
            while (j < this.population_size && flag > rates[j]) {
                j++;
            }
            if (!ids.contains(j)) {
                ids.add(j);
                Individual individual = this.population.get(j);
                individual.setId(newPopulation.size() + 1);
                newPopulation.add(individual);
            }
        }
        this.population = newPopulation;
    }

    private void recover() {
        this.population.sort(Comparator.comparing(Individual::getFitness));
        for (int i = 0; i < this.population_size / 10 && i < basePopulation.size(); i++) {
            this.population.set(i, basePopulation.get(i));
        }

    }

    // 交叉算子
    private void cross() {
        int range = this.population.size();
        for (int i = range; i < this.population_size; i++) {
            Individual individual = new Individual();
            individual.setId(i);
            int index_father = secureRandom.nextInt(range);
            int index_mother = secureRandom.nextInt(range);
            while (index_father == index_mother) {
                index_mother = secureRandom.nextInt(range);
            }
            Individual father = population.get(index_father);
            Individual mother = population.get(index_mother);
            int problem_index = father.getQuestions().size();

            Set<Integer> ids = new HashSet<>();
            for (int j = 0; j < problem_index / 2; j++) {
                int change_problem_index = secureRandom.nextInt(problem_index);
                while (ids.contains(change_problem_index)) {
                    change_problem_index = secureRandom.nextInt(problem_index);
                }
                ids.add(change_problem_index);
            }
            for (int j = 0; j < problem_index; j++) {
                if (ids.contains(j)) individual.getQuestions().add(father.getQuestions().get(j));
                else individual.getQuestions().add(mother.getQuestions().get(j));
            }
            this.population.add(individual);
        }
    }

    // 变异算子
    private void change() {
        for (int i = 0; i < this.population.size(); i++) {
            double flag = secureRandom.nextDouble();
            if (flag < this.change_rate) {
                Individual individual = this.population.get(i);
                int type = secureRandom.nextInt(this.questionTypes);
                int t = this.nums[type] / 3;
                if (this.nums[type] <= 0) t = 0;
                else if (t == 0) t = 1;
                if (t > 0) {
                    int range = secureRandom.nextInt(t);
                    Set<Long> ids = new HashSet<>();
                    individual.getQuestions().forEach(item -> {
                        ids.add(item.getId());
                    });
                    for (int j = 0; j < range; j++) {
                        int index = secureRandom.nextInt(this.type_nums[type]);
                        Question change_question = this.questionList.get(type).get(index);
                        while (ids.contains(change_question.getId())) {
                            index = secureRandom.nextInt(this.type_nums[type]);
                            change_question = this.questionList.get(type).get(index);
                        }
                        int pre_index = secureRandom.nextInt(this.nums[type]);
                        Question pre_question = this.questionList.get(type).get(pre_index);
                        ids.remove(pre_question.getId());
                        individual.getQuestions().remove(pre_index);
                        individual.getQuestions().add(change_question);
                        ids.add(change_question.getId());
                    }
                }
            }
        }
    }

    private boolean isEnd() {
        for (Individual individual : this.population) {
            if (individual.getFitness() > pre_fitness) return true;
        }
        return false;
    }

    private void loop() {
        this.fitnessList = new ArrayList<>();
        this.averageFitnessList = new ArrayList<>();
        int i = 0;
        for (; i < this.loops; i++) {
            List<Double> fitnessList =  get_Fitness();
            double max_fitness = fitnessList.get(0);
            double average_fitness = 0;
            for (double fitness : fitnessList) {
                if(fitness > max_fitness) {
                    max_fitness = fitness;
                }
                average_fitness += fitness;
            }
            average_fitness /= fitnessList.size();
            this.fitnessList.add(max_fitness);
            this.averageFitnessList.add(DataUtil.des(average_fitness));
            select();
            cross();
//            if (isEnd()) break;
            change();
        }
        log.info("迭代了{}次", i);
        this.iterationNum = i;
    }


    private void new_loop() {
        this.fitnessList = new ArrayList<>();
        this.averageFitnessList = new ArrayList<>();
        int i = 0;
        for (; i < this.loops; i++) {
            List<Double> fitnessList =  get_Fitness();
            double max_fitness = fitnessList.get(0);
            double average_fitness = 0;
            for (double fitness : fitnessList) {
                if(fitness > max_fitness) {
                    max_fitness = fitness;
                }
                average_fitness += fitness;
            }
            average_fitness /= fitnessList.size();
            this.fitnessList.add(max_fitness);
            this.averageFitnessList.add(DataUtil.des(average_fitness));
            new_select();
            cross();
            change();
            recover();
        }
        log.info("迭代了{}次", i);
        this.iterationNum = i;
    }

    @Override
    public PaperResult get_Paper(int stuid, double difficulty, int[] nums) {
        for (int i = 0; i < questionTypes; i++) {
            if (i < nums.length) this.nums[i] = nums[i];
            else this.nums[i] = 0;
        }
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        //初始化种群
        init_paper(nums);
        this.stuid = stuid;
        this.diff = difficulty;
        loop();

        double max_fitness = 0.0;
        int max_index = 0;
        int i = 0;
        for (Individual individual : this.population) {
            if (individual.getFitness() > max_fitness) {
                max_fitness = individual.getFitness();
                max_index = i;
            }
            i++;
        }
        Individual individual = this.population.get(max_index);
        PaperResult paperResult = new PaperResult(individual, this.iterationNum, this.knowledgeCoverage);
        stopWatch.stop();
        StringBuilder str = new StringBuilder();
        for(double item : this.fitnessList) {
            str.append(item).append(",");
        }
        StringBuilder str1 = new StringBuilder();
        for(double item : this.averageFitnessList) {
            str1.append(item).append(",");
        }
        paperResult.setMaxFitness(str.toString());
        paperResult.setAverageFitness(str1.toString());
        String path1 = "D:\\projects\\IDEAProjects\\NetworkExperimentation\\1.txt";
        String path2 = "D:\\projects\\IDEAProjects\\NetworkExperimentation\\2.txt";
        try (FileWriter fileWriter = new FileWriter(path1)) {
            fileWriter.append(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
        try (FileWriter fileWriter = new FileWriter(path2)) {
            fileWriter.append(str1);
        } catch (IOException e) {
            e.printStackTrace();
        }
        log.info("运行时间为{}ms", stopWatch.getTotalTimeMillis());
        log.info("适应度为{}", max_fitness);
        return paperResult;
    }

    @Override
    public PaperResult get_new_Paper(int stuid, double difficulty, int[] nums) {

        for (int i = 0; i < questionTypes; i++) {
            if (i < nums.length) this.nums[i] = nums[i];
            else this.nums[i] = 0;
        }
        init_paper(nums);
        this.stuid = stuid;
        this.diff = difficulty;
        new_loop();

        double max_fitness = 0.0;
        int max_index = 0;
        int i = 0;
        for (Individual individual : this.population) {
            if (individual.getFitness() > max_fitness) {
                max_fitness = individual.getFitness();
                max_index = i;
            }
            i++;
        }

        Individual individual = this.population.get(max_index);
        PaperResult paperResult = new PaperResult(individual, this.iterationNum, this.knowledgeCoverage);
        StringBuilder str = new StringBuilder();
        for(double item : this.fitnessList) {
            str.append(item).append(",");
        }
        StringBuilder str1 = new StringBuilder();
        for(double item : this.averageFitnessList) {
            str1.append(item).append(",");
        }
        paperResult.setMaxFitness(str.toString());
        paperResult.setAverageFitness(str1.toString());
        String path1 = "D:\\projects\\IDEAProjects\\NetworkExperimentation\\n1.txt";
        String path2 = "D:\\projects\\IDEAProjects\\NetworkExperimentation\\n2.txt";
        try (FileWriter fileWriter = new FileWriter(path1)) {
            fileWriter.append(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
        try (FileWriter fileWriter = new FileWriter(path2)) {
            fileWriter.append(str1);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return paperResult;
    }

    @Override
    public Boolean makeFakeData(int num) {
        SecureRandom secureRandom = new SecureRandom();
        String seed = "genetic_paper" + System.currentTimeMillis();
        secureRandom.setSeed(seed.getBytes());

        // 添加数据库条目，每种问题都等量的添加num
        for (int i = 0; i < questionTypes; i++) {
            List<Question> questionsList = questionMapper
                    .selectList(new QueryWrapper<Question>().eq("type", i));
            int range = questionsList.size();
            for (int j = 0; j < num; j++) {
                int index = secureRandom.nextInt(range);
                Question question = questionsList.get(index);
                question.setId(null);
                this.save(question);
            }
        }

        for (int j = 0; j < questionTypes; j++) {
            List<Question> questionsList = questionMapper
                    .selectList(new QueryWrapper<Question>().eq("type", j));
            int range = questionsList.size();
            for (Question question : questionsList) {
                double flag = secureRandom.nextDouble() + secureRandom.nextDouble();
                BigDecimal two = new BigDecimal(flag);
                flag = two.setScale(2, RoundingMode.HALF_UP).doubleValue();
                question.setDifficulty(flag);
                int flagID = secureRandom.nextInt(4);
                question.setInter(flagID);
                questionMapper.updateById(question);
            }
        }

        return true;
    }

}
